package com.googlecode.perfectable.prosperable.entry;

import static java.util.stream.Collectors.toList;
import static java.util.stream.StreamSupport.stream;

import javax.annotation.Nullable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;

import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.googlecode.perfectable.eventable.routing.Sink;
import com.googlecode.perfectable.eventable.routing.Source;
import com.googlecode.perfectable.introspection.Classes;
import com.googlecode.perfectable.introspection.Classes.ClassLoaderFunction;
import com.googlecode.perfectable.prosperable.channel.ChannelFactory;
import com.googlecode.perfectable.prosperable.message.ClientMessage;
import com.googlecode.perfectable.prosperable.message.ServerMessage;

public class ChannelServlet extends WebSocketServlet {
	private static final long serialVersionUID = -4609382746561846927L;

	private static final ClassLoaderFunction<ServerMessage> SERVER_CLASS_LOADER =
			Classes.loaderFunction(ServerMessage.class);
	private static final ClassLoaderFunction<ClientMessage> CLIENT_CLASS_LOADER =
			Classes.loaderFunction(ClientMessage.class);

	private static final String SERVER_MESSAGES_PARAMETER = "registeredServerMessages";
	private static final String CLIENT_MESSAGES_PARAMETER = "registeredClientMessages";

	private static final Splitter CLASS_NAMES_SPLITTER =
			Splitter.on(CharMatcher.WHITESPACE).trimResults().omitEmptyStrings();

	private ChannelFactory channelFactory;

	@Override
	public void init() throws ServletException {
		Iterable<Class<? extends ServerMessage>> serverClasses = this.extractClasses(SERVER_MESSAGES_PARAMETER,
				SERVER_CLASS_LOADER);
		Iterable<Class<? extends ClientMessage>> clientClasses = this.extractClasses(CLIENT_MESSAGES_PARAMETER,
				CLIENT_CLASS_LOADER);
		this.channelFactory = new ChannelFactory(serverClasses, clientClasses);
	}

	private <T> Iterable<Class<? extends T>> extractClasses(String parameterName,
			ClassLoaderFunction<T> classLoaderFunction) {
		String joined = getServletContext().getInitParameter(parameterName);
		if(joined == null) {
			return ImmutableList.of();
		}
		Iterable<String> names = CLASS_NAMES_SPLITTER.split(joined);
		Iterable<Class<? extends T>> classes = stream(names.spliterator(), false)
				.map(classLoaderFunction)
				.collect(toList());
		return classes;
	}

	@Override
	protected StreamInbound createWebSocketInbound(@Nullable String subProtocol, @Nullable HttpServletRequest request) {
		if(request == null) {
			throw new IllegalArgumentException();
		}
		HttpSession currentSession = request.getSession();
		if(currentSession == null) {
			throw new IllegalStateException();
		}
		Sink<ClientMessage> inbound = ChannelManager.extractInbound(currentSession);
		Source<ServerMessage> outbound = ChannelManager.extractOutbound(currentSession);
		return this.channelFactory.createChannel(inbound, outbound);
	}

}
